package cn.jhz.learn.community_dynamic.security.provider;


import cn.jhz.learn.community_dynamic.common.exception.VerificationCodeErrorException;
import cn.jhz.learn.community_dynamic.common.exception.VerificationCodeNotExistException;
import cn.jhz.learn.community_dynamic.security.AuthenticationParameterException;
import cn.jhz.learn.community_dynamic.security.model.PhoneSmsCodeAuthenticationToken;
import cn.jhz.learn.community_dynamic.security.service.JsonUserDetailsService;
import cn.jhz.learn.community_dynamic.manager.VerificationCodeManager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

@Component
public class JsonPhoneSmsCodeAuthenticationProvider implements AuthenticationProvider {

    protected final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private final VerificationCodeManager verificationCodeManager;
    private final JsonUserDetailsService userDetailsService;
    private final UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    private final UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    @Autowired
    public JsonPhoneSmsCodeAuthenticationProvider(JsonUserDetailsService userDetailsService,VerificationCodeManager verificationCodeManager) {
        this.userDetailsService = userDetailsService;
        this.verificationCodeManager = verificationCodeManager;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UserDetails user;
        // 确定用户名
        String phone = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();


        try{
            //校验动态码是否正确
            this.isValidCode(phone, (String) authentication.getCredentials());
        }catch (RuntimeException exception){
            throw new AuthenticationParameterException("短信验证码失效",exception);
        }

        try{
            //读取用户
            user = retrieveUser(phone, (PhoneSmsCodeAuthenticationToken) authentication);
        }catch (UsernameNotFoundException exception){
            /*用户不存在，直接注册*/
            this.userDetailsService.register(phone);
            user = retrieveUser(phone, (PhoneSmsCodeAuthenticationToken) authentication);
        }

        preAuthenticationChecks.check(user);
        postAuthenticationChecks.check(user);

        return createSuccessAuthentication(user, authentication, user);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.isAssignableFrom(PhoneSmsCodeAuthenticationToken.class);
    }

    protected final UserDetails retrieveUser(String username, PhoneSmsCodeAuthenticationToken authentication)
            throws AuthenticationException {
        try {
            UserDetails loadedUser = this.userDetailsService.loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException | InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    protected Authentication createSuccessAuthentication(Object principal,
                                                         Authentication authentication, UserDetails user) {
        PhoneSmsCodeAuthenticationToken result = new PhoneSmsCodeAuthenticationToken(
                principal, authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());

        return result;
    }

    private void isValidCode(String phone, String code){
        if(!this.verificationCodeManager.getInstance(phone).orElseThrow(VerificationCodeNotExistException::new).equals(code)) {
            /*验证码错误*/
            throw new VerificationCodeErrorException();
        }
    }

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
//                logger.debug("User account is locked");

                throw new LockedException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.locked",
                        "User account is locked"));
            }

            if (!user.isEnabled()) {
//                logger.debug("User account is disabled");

                throw new DisabledException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.disabled",
                        "User is disabled"));
            }

            if (!user.isAccountNonExpired()) {
//                logger.debug("User account is expired");

                throw new AccountExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.expired",
                        "User account has expired"));
            }
        }
    }

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
//                logger.debug("User account credentials have expired");

                throw new CredentialsExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
                        "User credentials have expired"));
            }
        }
    }
}
